package goutil

import (
	"fmt"
	"path"
	"strings"
	"time"

	"github.com/fsnotify/fsnotify"
	"github.com/spf13/viper"
)

// 子配置文件属性
type SubConfig struct {
	name   string // 子配置名
	path   string // 配置路径
	active bool   // 启用
}

// 配置信息结构体. 扩展Viper以支持多配置
type GoutilConfig struct {
	*viper.Viper

	// 以下成员重载时不会被重置
	subConfigs map[string]SubConfig // 子配置
	configFile string               // 主配置文件路径
	filePrefix string               // 路径/文件名前缀_
	fileExt    string               // 文件名后缀
	isReload   bool                 // 重载配置标志
	onReload   func(fsnotify.Event) // 重载处理.
}

func setAllAsDefault(dst, src *viper.Viper) {
	for k, v := range src.AllSettings() {
		dst.SetDefault(k, v)
	}
}

func (s *GoutilConfig) loadConfig(path string, tmp *viper.Viper) bool {
	if tmp == nil {
		tmp = s.Viper
	}
	setAllAsDefault(tmp, tmp)
	tmp.SetConfigFile(path)
	res := ""
	if err := tmp.ReadInConfig(); err != nil {
		res = fmt.Sprintf("Config [%s] load failed: %s.\n", path, err)
	}
	if s.isReload {
		if res != "" {
			fmt.Printf(time.Now().Format("2006-01-02 15:04:05  ") + res)
			return false
		}
		return true
	}
	if res != "" {
		panic(fmt.Errorf(time.Now().Format("2006-01-02 15:04:05  ") + res))
	}
	s.WatchConfig()
	s.OnConfigChange(s.reloadAll())
	return true
}

// 加载主配置文件
func (s *GoutilConfig) SetConfig(file string) {
	if s.isReload {
		panic(fmt.Errorf("In reload state, should never happen!\n"))
	}
	if s.Viper == nil {
		s.Viper = viper.New()
	}
	s.loadConfig(file, nil)
	filePath, fileName := path.Split(file)
	s.fileExt = path.Ext(fileName)
	s.filePrefix = filePath + strings.TrimSuffix(fileName, s.fileExt) + "_"
	s.configFile = file
	s.subConfigs = make(map[string]SubConfig)
}

// 加载一个或多个子配置.
//  subs: 子配置文件名. 前缀为主配置文件的`路径+文件名`, 后缀名同主配置文件
// tips:
//  - 加载子配置前必须先加载主配置文件.
//  - 多配置数据合并规则为向左覆盖.
//  - 不支持动态设置子配置
func (s *GoutilConfig) SetSubConfigs(subs ...string) {
	if s.configFile == "" {
		panic(fmt.Errorf("No config loaded. " +
			"It's required before load sub-configs.\n"))
	}
	if s.isReload {
		panic(fmt.Errorf("In reload state, should never happen!\n"))
	}
	for _, f := range subs {
		sub, ok := s.subConfigs[f]
		if ok {
			fmt.Printf("Duplicate sub-config: %s, skip!\n", f)
			continue
		}
		sub = SubConfig{
			name:   f,
			path:   s.filePrefix + f + s.fileExt,
			active: true,
		}
		s.subConfigs[f] = sub
		s.loadConfig(sub.path, nil)
	}
}

// 重载预设的所有配置文件
func (s *GoutilConfig) reloadAll() func(fsnotify.Event) {
	return func(e fsnotify.Event) {
		s.isReload = true
		tmp := viper.New()
		if ret := s.loadConfig(s.configFile, tmp); ret == false {
			return
		}
		for _, f := range s.subConfigs {
			if ret := s.loadConfig(f.path, tmp); ret == false {
				return
			}
		}
		setAllAsDefault(s.Viper, tmp)
		s.isReload = false

		if s.onReload != nil {
			s.onReload(e)
		}
	}
}

// 配置初始化
//  file: 主配置文件. 可以是相对路径
//  subs: 子配置文件后缀
func NewConfig(file string, subs ...string) *GoutilConfig {
	c := GoutilConfig{}
	c.SetConfig(file)
	c.SetSubConfigs(subs...)
	return &c
}

// 设置主/子配置文件被修改后的处理, 只需设置一次
func (s *GoutilConfig) OnConfigReload(f func(fsnotify.Event)) {
	s.onReload = f
}

// 获取所有配置数据
func (s *GoutilConfig) GetAll() map[string]interface{} {
	return s.AllSettings()
}
